#include "dockwidgethelper.h"

#include <QDockWidget>
#include <QTabBar>
#include <QBitArray>
#include <QHelpEvent>
#include <QToolTip>
#include <QLabel>
#include <QShortcut>
#include <QTextEdit>

#include "propertydefs.h"
#include "mainwindow.h"
#include "core/config.h"
#include "core/coreconfig.h"
#include "core/stitch.h"
#include "utils/iconutils.h"
using namespace stitch;

QString DockWidgetHelper::iconFileName(DockIndex p_dockIndex)
{
    switch (p_dockIndex)
    {
    case DockIndex::NavigationDock:
        return "navigation_dock.svg";
    case DockIndex::OutlineDock:
        return "outline_dock.svg";
    case DockIndex::HistoryDock:
        return "history_dock.svg";
    case DockIndex::TagDock:
        return "tag_dock.svg";
    case DockIndex::SearchDock:
        return "search_dock.svg";
    case DockIndex::SnippetDock:
        return "snippet_dock.svg";
    case DockIndex::LocationListDock:
        return "location_list_dock.svg";
    default:
        return QString();
    }
}

static int rotationAngle(Qt::DockWidgetArea p_area)
{
    switch (p_area)
    {
    case Qt::LeftDockWidgetArea:
        return 90;

    case Qt::RightDockWidgetArea:
        return 270;

    default:
        return -1;
    }
}
DockWidgetHelper::NavigationItemInfo::NavigationItemInfo(QTabBar *p_tabBar, int p_tabIndex, int p_dockIndex)
    : m_tabBar(p_tabBar),
      m_tabIndex(p_tabIndex),
      m_dockIndex(p_dockIndex)
{
}

DockWidgetHelper::NavigationItemInfo::NavigationItemInfo(int p_dockIndex)
    : m_dockIndex(p_dockIndex)
{
}

DockWidgetHelper::DockWidgetHelper(MainWindow *p_mainWindow)
    : QObject(p_mainWindow),
      NavigationMode(NavigationMode::Type::DoubleKeys, p_mainWindow),
      m_mainWindow(p_mainWindow)
{
}

void DockWidgetHelper::setupDocks()
{
    m_mainWindow->setTabPosition(Qt::LeftDockWidgetArea, QTabWidget::West);
    m_mainWindow->setTabPosition(Qt::RightDockWidgetArea, QTabWidget::East);
    m_mainWindow->setTabPosition(Qt::TopDockWidgetArea, QTabWidget::North);
    m_mainWindow->setTabPosition(Qt::BottomDockWidgetArea, QTabWidget::North);
    m_mainWindow->setDockNestingEnabled(true);

    m_dockIcons.resize(DockIndex::MaxDock);

    // The order of m_docks should be identical with enum DockIndex.
    QVector<int> tabifiedDockIndex;

    tabifiedDockIndex.append(m_docks.size());
    setupNavigationDock();

    tabifiedDockIndex.append(m_docks.size());
    setupHistoryDock();

    tabifiedDockIndex.append(m_docks.size());
    setupTagDock();

    tabifiedDockIndex.append(m_docks.size());
    setupSearchDock();

    tabifiedDockIndex.append(m_docks.size());
    setupSnippetDock();

    setupOutlineDock();

    setupConsoleDock();

    setupLocationListDock();

    setupShortcuts();

    qDebug() << tabifiedDockIndex.size();
    for (int i = 1; i < tabifiedDockIndex.size(); ++i)
    {
        m_mainWindow->tabifyDockWidget(m_docks[tabifiedDockIndex[i - 1]], m_docks[tabifiedDockIndex[i]]);
    }
}

QVector<void *> DockWidgetHelper::getVisibleNavigationItems()
{
    m_navigationItems.clear();

    QBitArray tabifiedDocks(m_docks.size(), false);
    Q_FOREACH (QTabBar *tabBar, m_mainWindow->findChildren<QTabBar *>(QString(), Qt::FindDirectChildrenOnly))
    {
        if (!tabBar->isVisible())
        {
            continue;
        }

        const int cnt = tabBar->count();
        for (int i = 0; i < cnt; ++i)
        {
            auto dock = reinterpret_cast<QDockWidget *>(tabBar->tabData(i).toULongLong());
            if (!dock)
            {
                continue;
            }
            int dockIdx = dock->property(PropertyDefs::c_dockWidgetIndex).toInt();
            tabifiedDocks.setBit(dockIdx);

            m_navigationItems.push_back(NavigationItemInfo(tabBar, i, dockIdx));
        }
    }

    // Non-tabified docks.
    for (int i = 0; i < m_docks.size(); ++i)
    {
        if (!tabifiedDocks[i] && m_docks[i]->isVisible())
        {
            m_navigationItems.push_back(NavigationItemInfo(i));
        }
    }

    QVector<void *> items;
    for (auto &item : m_navigationItems)
    {
        items.push_back(&item);
    }
    return items;
}

void DockWidgetHelper::placeNavigationLabel(int p_idx, void *p_item, QLabel *p_label)
{
    Q_UNUSED(p_idx);
    auto info = static_cast<NavigationItemInfo *>(p_item);
    if (info->m_tabBar)
    {
        auto pos = info->m_tabBar->tabRect(info->m_tabIndex).topLeft();
        pos = info->m_tabBar->mapToGlobal(pos);
        p_label->move(m_mainWindow->mapFromGlobal(pos));
    }
    else
    {
        p_label->setParent(m_docks[info->m_dockIndex]);
        p_label->move(0, 0);
    }
}

void DockWidgetHelper::handleTargetHit(void *p_item)
{
    auto info = static_cast<NavigationItemInfo *>(p_item);
    activateDock(static_cast<DockIndex>(info->m_dockIndex));
}

void DockWidgetHelper::clearNavigation()
{
    NavigationMode::clearNavigation();

    m_navigationItems.clear();
}

bool DockWidgetHelper::eventFilter(QObject *p_obj, QEvent *p_event)
{
    if (p_event->type() == QEvent::ToolTip)
    {
        // The QTabBar of the tabified dock widgets does not show tooltip due to Qt's internal implementation.
        auto helpEve = static_cast<QHelpEvent *>(p_event);
        auto tabBar = static_cast<QTabBar *>(p_obj);
        int idx = tabBar->tabAt(helpEve->pos());
        bool done = false;
        if (idx > -1)
        {
            auto dock = reinterpret_cast<QDockWidget *>(tabBar->tabData(idx).toULongLong());
            if (dock)
            {
                done = true;
                QToolTip::showText(helpEve->globalPos(), dock->property(PropertyDefs::c_dockWidgetTitle).toString());
            }
        }

        if (!done)
        {
            QToolTip::hideText();
            p_event->ignore();
        }

        return true;
    }

    return QObject::eventFilter(p_obj, p_event);
}

void DockWidgetHelper::activateDock(DockIndex p_dockIndex)
{
    Q_ASSERT(p_dockIndex < DockIndex::MaxDock);
    activateDock(getDock(p_dockIndex));
}

void DockWidgetHelper::activateDock(QDockWidget *p_dock)
{
    p_dock->show();
    Q_FOREACH (QTabBar *tabBar, m_mainWindow->findChildren<QTabBar *>(QString(), Qt::FindDirectChildrenOnly))
    {
        bool found = false;
        for (int i = 0; i < tabBar->count(); ++i)
        {
            if (p_dock == reinterpret_cast<QWidget *>(tabBar->tabData(i).toULongLong()))
            {
                tabBar->setCurrentIndex(i);
                found = true;
                break;
            }
        }

        if (found)
        {
            break;
        }
    }
    p_dock->setFocus();
}

QDockWidget *DockWidgetHelper::getDock(DockIndex p_dockIndex) const
{
    Q_ASSERT(p_dockIndex < DockIndex::MaxDock);
    return m_docks[p_dockIndex];
}

void DockWidgetHelper::postSetup()
{
    updateDockWidgetTabBar();

    for (const auto dock : m_docks)
    {
        connect(dock, &QDockWidget::dockLocationChanged,
                this, &DockWidgetHelper::updateDockWidgetTabBar);
        connect(dock, &QDockWidget::topLevelChanged,
                this, &DockWidgetHelper::updateDockWidgetTabBar);
    }
}

void DockWidgetHelper::updateDockWidgetTabBar()
{
    QBitArray tabifiedDocks(m_docks.size(), false);
    Q_FOREACH (QTabBar *tabBar, m_mainWindow->findChildren<QTabBar *>(QString(), Qt::FindDirectChildrenOnly))
    {
        if (!m_tabBarsMonitored.contains(tabBar))
        {
            m_tabBarsMonitored.insert(tabBar);
            tabBar->installEventFilter(this);
        }

        tabBar->setDrawBase(false);

        const int sz = Config::getInst().getCoreConfig().getDocksTabBarIconSize();
        tabBar->setIconSize(QSize(sz, sz));

        auto tabShape = tabBar->shape();
        bool iconOnly = tabShape == QTabBar::RoundedWest || tabShape == QTabBar::RoundedEast || tabShape == QTabBar::TriangularWest || tabShape == QTabBar::TriangularEast;
        const int cnt = tabBar->count();
        if (cnt == 1)
        {
            iconOnly = false;
        }
        for (int i = 0; i < cnt; ++i)
        {
            auto dock = reinterpret_cast<QDockWidget *>(tabBar->tabData(i).toULongLong());
            if (!dock)
            {
                continue;
            }
            int dockIdx = dock->property(PropertyDefs::c_dockWidgetIndex).toInt();
            tabifiedDocks.setBit(dockIdx);
            if (iconOnly)
            {
                dock->setWindowTitle(QString());
            }
            else if (dock->windowTitle().isEmpty())
            {
                dock->setWindowTitle(dock->property(PropertyDefs::c_dockWidgetTitle).toString());
            }
            tabBar->setTabIcon(i, getDockIcon(static_cast<DockIndex>(dockIdx)));
        }
    }

    // Non-tabified docks.
    for (int i = 0; i < m_docks.size(); ++i)
    {
        if (!tabifiedDocks[i] && m_docks[i]->windowTitle().isEmpty())
        {
            m_docks[i]->setWindowTitle(m_docks[i]->property(PropertyDefs::c_dockWidgetTitle).toString());
        }
    }

    emit m_mainWindow->layoutChanged();
}

void DockWidgetHelper::setupNavigationDock()
{
}

void DockWidgetHelper::setupOutlineDock()
{
}

void DockWidgetHelper::setupConsoleDock()
{
}

void DockWidgetHelper::setupSearchDock()
{
}

void DockWidgetHelper::setupSnippetDock()
{
}

void DockWidgetHelper::setupHistoryDock()
{
}

void DockWidgetHelper::setupTagDock()
{
}

void DockWidgetHelper::setupLocationListDock()
{
}

void DockWidgetHelper::setupShortcuts()
{
}

const QIcon &DockWidgetHelper::getDockIcon(DockIndex p_dockIndex)
{
    static const auto fg = Stitch::getInst().getThemeMgr().paletteColor("widgets#mainwindow#dockwidget_tabbar#icon#fg");
    static const auto selectedFg = Stitch::getInst().getThemeMgr().paletteColor("widgets#mainwindow#dockwidget_tabbar#icon#selected#fg");

    const auto area = m_mainWindow->dockWidgetArea(m_docks[p_dockIndex]);
    const int newAngle = rotationAngle(area);
    if (m_dockIcons[p_dockIndex].m_rotationAngle != newAngle && area != Qt::NoDockWidgetArea)
    {
        QVector<IconUtils::OverriddenColor> colors;
        colors.push_back(IconUtils::OverriddenColor(fg, QIcon::Normal));
        // FIXME: the Selected Mode is not used by the selected tab of a QTabBar.
        colors.push_back(IconUtils::OverriddenColor(selectedFg, QIcon::Selected));

        auto iconFile = Stitch::getInst().getThemeMgr().getIconFile(iconFileName(p_dockIndex));
        m_dockIcons[p_dockIndex].m_icon = IconUtils::fetchIcon(iconFile, colors, newAngle);
        m_dockIcons[p_dockIndex].m_rotationAngle = newAngle;
    }

    return m_dockIcons[p_dockIndex].m_icon;
}
